/***************************************************************
*  Copyright (c) 2007 by GroupMe! Team (www.groupme.net)
*  All rights reserved.
*
*  This file is part of the GroupMe! Project. Source code of 
*  this project is closed and redistribution of this code is
*  prohibited. 
*  
*  Contact: http://www.groupme.net
*
*  This copyright notice MUST APPEAR in all copies of the file!
***************************************************************/
package net.groupme.group;

import java.net.MalformedURLException;
import java.net.URL;
import java.util.Iterator;
import java.util.StringTokenizer;

import net.groupme.exceptions.AddingResourceFailedException;
import net.groupme.exceptions.DeleteFailedException;
import net.groupme.exceptions.GroupVisualizationException;
import net.groupme.exceptions.InsertFailedException;
import net.groupme.exceptions.UpdateFailedException;
import net.groupme.gui.rendering.DefaultRenderer;
import net.groupme.gui.rendering.RenderingUtility;
import net.groupme.gui.rendering.constants.RENDERING_CONSTANTS;
import net.groupme.model.Group;
import net.groupme.model.GroupVisualization;
import net.groupme.model.MODEL_CONSTANTS;
import net.groupme.model.ObjectFactory;
import net.groupme.model.Resource;
import net.groupme.model.ResourceVisualization;
import net.groupme.model.Tag;
import net.groupme.model.User;
import net.groupme.user.UserManager;
import net.groupme.utils.sql.GroupQueryingUtility;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;


/**
 * created on Jun 27, 2007
 * 
 * This class manages a Group which is actually modified by a user.
 * 
 * @author Fabian Abel, <a href="mailto:abel@l3s.de">abel@l3s.de</a>
 * @author last edited by: $Author: fabian $
 * 
 * @version $Revision: 1.26 $ $Date: 2009-04-22 18:28:55 $
 */
public class GroupManager {
	
	/** Logger for this class and subclasses */
	protected final Log logger = LogFactory.getLog(getClass());

	/** The group which is managed by this class.*/
	private Group group = null;
	
	/** reference to the {@link UserManager} */
	private UserManager userManager = null;
	
	/** the actual {@link GroupVisualization} of the managed group */
	private GroupVisualization groupVisualization = null;

	/**
	 * Creates a new GroupManager instance. 
	 */
	public GroupManager(UserManager userManager) {
		super();
		this.userManager = userManager;
	}

	/**
	 * This method returns the group of a GroupManager object.
	 * @return the group
	 */
	public Group getGroup() {
		return group;
	}

	/**
	 * This method sets the group of a GroupManager object.
	 * @param group the group to set
	 */
	public void setGroup(Group group) {
		this.group = group;
	}

	/**
	 * This method returns only the user that is managed by the the user manager 
	 * @return the manager
	 */
	public User getManager() {
		return userManager.getUser();
	}	
	
	/**
	 * This method returns the entire user manager 
	 * @return the manager
	 */
	public UserManager getUserManager() {
		return userManager;
	}
	
	/**
	 * This method sets the manager of a GroupManager object.
	 * @param manager the user manager
	 */
	public void setUserManager(UserManager manager) {
		this.userManager = manager;
	}
	
	/**
	 * This method returns the actual groupVisualization of a GroupManager object.
	 * @return the actual groupVisualization
	 */
	public GroupVisualization getGroupVisualization() {
		return groupVisualization;
	}

	/**
	 * This method sets the actual groupVisualization of a GroupManager object.
	 * @param groupVisualization the actual groupVisualization to set
	 */
	public void setGroupVisualization(GroupVisualization groupVisualization) {
		this.groupVisualization = groupVisualization;
	}

	/**
	 * This method...
	 * <ol>
	 *   <li>creates a new Group</li>
	 *   <li>sets this group as being managed by this manager instance (calls  {@link GroupManager#setGroup(Group)})</li>
	 *   <li>adds the new group to the actual user</li>
	 *   <li>creates a new {@link GroupVisualization} for the group.</li>
	 *   <li>sets the actual {@link GroupVisualization} instance of this class to the created one...</li>
	 *</ol>
	 *  
	 * @param name the name of the new group (may be <code>null</code>)
	 * @param description a short description of the group (may be <code>null</code>)
	 * @return <code>true</code> if Group was created successfully, otherwise <code>false</code> 
	 */
	public boolean createNewGroup(String name, String description){
//		TODO - BEGIN HACK:

		if(name == null | name.trim() == ""){
			name = "Unnamed Group";
		}
		if(description == null){
			description = "";
		}
//		END
		
		try {
			// Name and description can not be null because of the testing above
				this.group = ObjectFactory.createGroup(name, description);
				System.out.println(userManager.getUser());
				this.groupVisualization = ObjectFactory.createGroupVisualization("Visualization of " + name, "It is maintained by " + userManager.getUser().getUserName());
			
			this.getManager().addGroup(this.group, MODEL_CONSTANTS.ROLE_OWNER);
			this.group.addVisualization(groupVisualization, this.getManager());
		} catch (InsertFailedException e) {
			e.printStackTrace();
			return false;
		}
		return true;
	}
	
	/**
	 * Creates a new Resource and adds it to the {@link Group} which is managed by this {@link GroupManager}. At second this 
	 * method also creates a new {@link ResourceVisualization} for the added Resource which points to the {@link DefaultRenderer}.
	 * 
	 * @param name the name/title of the resource
	 * @param description a short description
	 * @param type the resource type as defined in {@link MODEL_CONSTANTS}
	 * @param url the URL of the resource
	 * @return the {@link ResourceVisualization} object (equipped with dummy values!) of the created resource
	 * @throws AddingResourceFailedException thrown if there are problems on creating the resource, adding it to the group or creating the {@link ResourceVisualization} object
	 */
	public ResourceVisualization addResource(String name, String description, String type,  String url) throws AddingResourceFailedException {
		Resource resource = null;
		//TODO - BEGIN HACK:
		if(name == null){
			name = "";
		}
		if(description == null){
			description = "";
		}
		logger.info("CONTENT-TYPE: " + type);
		
		if(type == null){
			type = MODEL_CONSTANTS.RESOURCE_TYPE_WEBSITE;
		}
		if(url == null){
			url = "";
		}
//		END HACK:

		//1. create resource:
		try {
			resource = ObjectFactory.createResource(name, description, type, new URL(url));
		} catch (MalformedURLException e) {
			e.printStackTrace();
			throw new AddingResourceFailedException("URL of the Resource to add is not valid.", e);
		} catch (InsertFailedException e) {
			e.printStackTrace();
			throw new AddingResourceFailedException("Insert operation of the new Resource failed.", e);
		}
		//2. add resource to group:
		try {
			this.group.addResource(resource);
		} catch (InsertFailedException e) {
			e.printStackTrace();
			throw new AddingResourceFailedException("Creation of new resource was successful, but adding the resource to the Group failed.", e);
		}
		//3. create resource visualization, automatically added to the GroupVisualization:
		ResourceVisualization resVisu = null;
		try {
			try {
				resVisu = ObjectFactory.createResourceVisualization((this.groupVisualization == null ? this.group.getActualGroupVisualization(this.getManager().getId()) : this.groupVisualization), resource);
				resVisu.setRenderer(RenderingUtility.suggestRenderer(type));
			} catch (GroupVisualizationException e) {
				e.printStackTrace();
			}
		} catch (InsertFailedException e) {
			e.printStackTrace();
			throw new AddingResourceFailedException("Creation of new resource and adding it to the group was successful, but creating the ResourceVisualization failed.", e);
		}
		//4. set standard width for the ResourceVisualization
		try {
			resVisu.setWidth(RENDERING_CONSTANTS.STANDARD_WIDTH_OF_RESOURCE_VISUALIZATIONS);
		} catch (UpdateFailedException e) {
			e.printStackTrace();
			throw new AddingResourceFailedException("Creation of new resource, adding it to the group and creating the ResourceVisualization was successful, but setting standard width failed.", e);
		}
		return resVisu;
	}
	
	
	/**
	 * Creates a new Resource and adds it to the {@link Group} which is managed by this {@link GroupManager}. At second this 
	 * method also creates a new {@link ResourceVisualization} for the added Resource which points to the {@link DefaultRenderer}.
	 * 
	 * @param name the name/title of the resource
	 * @param description a short description
	 * @param type the resource type as defined in {@link MODEL_CONSTANTS}
	 * @param url the URL of the resource
	 * @return the {@link ResourceVisualization} object (equipped with dummy values!) of the created resource
	 * @throws AddingResourceFailedException thrown if there are problems on creating the resource, adding it to the group or creating the {@link ResourceVisualization} object
	 */
	public ResourceVisualization addGroupMeResource(Resource resource) throws AddingResourceFailedException {
		//1. create resource:
		//allready there...
		
		//2. add resource to group:
		try {
			this.group.addResource(resource);
		} catch (InsertFailedException e) {
			e.printStackTrace();
			throw new AddingResourceFailedException("Creation of new resource was successful, but adding the resource to the Group failed.", e);
		}
		//3. create resource visualization, automatically added to the GroupVisualization:
		ResourceVisualization resVisu;
		try {
			resVisu = ObjectFactory.createResourceVisualization(this.groupVisualization, resource);
		} catch (InsertFailedException e) {
			e.printStackTrace();
			throw new AddingResourceFailedException("Creation of new resource and adding it to the group was successful, but creating the ResourceVisualization failed.", e);
		}
		//4. set standard width for the ResourceVisualization
		try {
			resVisu.setWidth(RENDERING_CONSTANTS.STANDARD_WIDTH_OF_RESOURCE_VISUALIZATIONS);
		} catch (UpdateFailedException e) {
			e.printStackTrace();
			throw new AddingResourceFailedException("Creation of new resource, adding it to the group and creating the ResourceVisualization was successful, but setting standard width failed.", e);
		}
		return resVisu;
	}
	
	
	/**
	 * Returns the ResourceVisualizations of the GroupVisualization as an Array.
	 * @return an Array of the ResourceVisualizations of the group.
	 */
	public ResourceVisualization[] getResourceVisualizations(){
		ResourceVisualization[] resVisus = this.groupVisualization.getResourceVisualizations().toArray(new ResourceVisualization[0]);
		return resVisus;
	}
	
	/**
	 * Adds the Tag with the given Keyword to the Resource with the given ID.
	 * @param keyword the Keyword of the Tag
	 * @param resourceID the ID of the Resource which should be tagged
	 * @return true if successful, otherwise false
	 */
	public Tag[] addTagToResource(String orgKeyword, String resourceID){
		if (orgKeyword.contains(",")||orgKeyword.contains(";")){
			Resource resource = ObjectFactory.getResource(resourceID);
			StringTokenizer tokenizer = new StringTokenizer(orgKeyword, (",;"));
			while (tokenizer.hasMoreTokens()){
				String keyword = tokenizer.nextToken().toLowerCase().trim();
				logger.info("Adding tag: " + keyword + " to resource: " +resourceID);
				try {
					Tag tag = ObjectFactory.getTagForKeyword(keyword);
					resource.addTag(this.getManager(), tag, this.group);
					//TODO: recompute Tag Clouds:
					//normalerweise sollte hier nen neuberechnung der gecacheten Tag clouds hin, allerdings
					//momentan wird das einfach bei getTagCloud gemacht...
				} catch (InsertFailedException e) {
					e.printStackTrace();
					return new Tag[0];
				}				
			}
			return resource.getTags(this.getManager(), this.group).toArray(new Tag[0]);
		}
	
		String keyword = orgKeyword.toLowerCase().trim();
		logger.info("Adding tag: " + keyword + " to resource: " +resourceID);
		try {
			Tag tag = ObjectFactory.getTagForKeyword(keyword);
			Resource resource = ObjectFactory.getResource(resourceID);
			resource.addTag(this.getManager(), tag, this.group);
			//TODO: recompute Tag Clouds:
			//normalerweise sollte hier nen neuberechnung der gecacheten Tag clouds hin, allerdings
			//momentan wird das einfach bei getTagCloud gemacht...
			return resource.getTags(this.getManager(), this.group).toArray(new Tag[0]);
		} catch (InsertFailedException e) {
			e.printStackTrace();
			return new Tag[0];
		}
	}
	
	
	/**
	 * Adds a tag (which corresponds to the given Keyword) to a the group which is managed by 
	 * this {@link GroupManager} instance. The tagger will be the actual user.
	 * 
	 * @param keyword the keyword of the Tag
	 * @return <code>true</code> if successful, otherwise <code>false</code>
	 */
	public boolean addTagToActualGroup(String orgKeyword){
		if (orgKeyword.contains(",")||orgKeyword.contains(";")){
			
			StringTokenizer tokenizer = new StringTokenizer(orgKeyword, (",;"));
			while (tokenizer.hasMoreTokens()){
				String keyword = tokenizer.nextToken().toLowerCase().trim();
				try {
					Tag tag = ObjectFactory.getTagForKeyword(keyword);
					this.group.addTag(this.getManager(), tag);
					//TODO: recompute Tag Clouds:
					//normalerweise sollte hier nen neuberechnung der gecacheten Tag clouds hin, allerdings
					//momentan wird das einfach bei getTagCloud gemacht...
				} catch (InsertFailedException e) {
					e.printStackTrace();
					return false;
				}
			}
			return true;
		}
		
		String keyword = orgKeyword.toLowerCase().trim();
		logger.info("Adding tag: " + keyword);
		try {
			Tag tag = ObjectFactory.getTagForKeyword(keyword);
			this.group.addTag(this.getManager(), tag);
			//TODO: recompute Tag Clouds:
			//normalerweise sollte hier nen neuberechnung der gecacheten Tag clouds hin, allerdings
			//momentan wird das einfach bei getTagCloud gemacht...
			return true;
		} catch (InsertFailedException e) {
			e.printStackTrace();
			return false;
		}
	}
	
	/**
	 * Adds a Thumbnail to a the group which is managed by 
	 * this {@link GroupManager} instance. 
	 * 
	 * @param the Thumbnail Url for this Group
	 * @return <code>true</code> if successful, otherwise <code>false</code>
	 */
	public boolean addThumbnailToActualGroup(String thumbnail){
		logger.info("adding a Thumbnail to a Group");
		try {
			this.groupVisualization.setThumbnail(thumbnail);
			return true;

		} catch (UpdateFailedException e) {
			e.printStackTrace();
			return false;
		}
	}
	
	/**
	 * Returns all Tags of a given Resource for this User and this Group.
	 * @param resourceID the ID of the tagged Resource
	 * @return an Array of {@link Tag}
	 */
	public Tag[] getTags(String resourceID){
		return ObjectFactory.getResource(resourceID).getTags(this.getManager(), this.group)
		.toArray(new Tag[0]);
	}
	
	/**
	 * Returns tags of the group which is actally managed by the {@link GroupManager} instance.
	 * <i>As this method is only called if the group is not included in another group, we don't
	 * need to specify the group context.</i>
	 * 
	 * @param allTags if this parameter is <code>false</code>, then we only search for tags that were
	 * inserted by the actual user, otherwise (if this parameter is <code>true</code>) all tags are
	 * considered.
	 * @return an Array of {@link Tag} instances
	 */
	public Tag[] getTagsOfGroup(boolean allTags){
		if(allTags){
			return this.group.getTags().toArray(new Tag[0]); 
		}else{
			return this.group.getTags(this.getManager()).toArray(new Tag[0]);
		}
	}
	
	/**
	 * Removes a given ResourceVisualization and its linked Resource from the GroupVisualization
	 * and the Group.
	 * @param resVisuID the ID of the ResourceVisualization
	 * @return <code>true</code> if removing was successfull, otherwise <code>false</code>
	 */
	public boolean removeResourceAndVisualization(String resVisuID){
		ResourceVisualization resVisu = ObjectFactory.getResourceVisualization(resVisuID);
		try {
			Resource tempRessource = resVisu.getResource();
			this.groupVisualization.removeResourceVisualization(resVisu);
			this.group.removeResource(tempRessource);
			return true;
		} catch (DeleteFailedException e) {
			e.printStackTrace();
			return false;
		}
		
	}
	
	/**
	 * Returns the real ID of the group.
	 * @return the ID as a String
	 */
	public String getGroupID(){
		return this.group.getId();
	}
	
	/**
	 * Function to check if the User is Subscriber of the current Group
	 * @return <code>true</code> if the User if Subscriber of the current Group, otherwise <code>false</code>
	 */
	public boolean isSubscriberOfGroup(){
		String[] subscriber = {MODEL_CONSTANTS.ROLE_SUBSCRIBER};
		if (this.getManager() !=  null && this.group != null){
			Iterator iter = GroupQueryingUtility.getAllGroupsOfUser(this.getManager(), subscriber).iterator();
			while (iter.hasNext()) {
				if(((Group)iter.next()).getId().equals(this.group.getId())){
					return true;
				}
			}
		}
		return false;
	}
	
	/**
	 * Function to check if the User is the owner of the current Group
	 * @return <code>true</code> if the User is the owner of the current Group, otherwise <code>false</code>
	 */
	public boolean isOwnerOfGroup(){
		if (this.getManager() !=  null && this.group != null){
			Iterator iter = GroupQueryingUtility.getAllUsersOfGroup(this.group.getId(), MODEL_CONSTANTS.ROLE_OWNER).iterator();
			while (iter.hasNext()) {
				if(((User)iter.next()).getId().equals(this.getManager().getId())){
					return true;
				}
			}
		}
		return false;
	}
	
	/**
	 * Function to check if the User is an editor of the current Group
	 * @return <code>true</code> if the User is an editor of the current Group, otherwise <code>false</code>
	 */
	public boolean isEditorOfGroup(){
		if (this.getManager() !=  null && this.group != null){
			Iterator iter = GroupQueryingUtility.getAllUsersOfGroup(this.group.getId(), MODEL_CONSTANTS.ROLE_EDITOR).iterator();
			while (iter.hasNext()) {
				if(((User)iter.next()).getId().equals(this.getManager().getId())){
					return true;
				}
			}
		}
		return false;
	}
}
